/*
 * Javassist, a Java-bytecode translator toolkit.
 * Copyright (C) 1999- Shigeru Chiba. All Rights Reserved.
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License.  Alternatively, the contents of this file may be used under
 * the terms of the GNU Lesser General Public License Version 2.1 or later,
 * or the Apache License Version 2.0.
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 */

package com.feilong.lib.javassist.expr;

import com.feilong.lib.javassist.CannotCompileException;
import com.feilong.lib.javassist.CtClass;
import com.feilong.lib.javassist.bytecode.BadBytecode;
import com.feilong.lib.javassist.bytecode.CodeAttribute;
import com.feilong.lib.javassist.bytecode.CodeIterator;
import com.feilong.lib.javassist.bytecode.ExceptionTable;
import com.feilong.lib.javassist.bytecode.MethodInfo;
import com.feilong.lib.javassist.bytecode.Opcode;

/**
 * A translator of method bodies.
 *
 * <p>
 * The users can define a subclass of this class to customize how to
 * modify a method body. The overall architecture is similar to the
 * strategy pattern.
 *
 * <p>
 * If <code>instrument()</code> is called in
 * <code>CtMethod</code>, the method body is scanned from the beginning
 * to the end.
 * Whenever an expression, such as a method call and a <code>new</code>
 * expression (object creation),
 * is found, <code>edit()</code> is called in <code>ExprEdit</code>.
 * <code>edit()</code> can inspect and modify the given expression.
 * The modification is reflected on the original method body. If
 * <code>edit()</code> does nothing, the original method body is not
 * changed.
 *
 * <p>
 * The following code is an example:
 *
 * <pre>
 * CtMethod cm = ...;
 * cm.instrument(new ExprEditor() {
 *     public void edit(MethodCall m) throws CannotCompileException {
 *         if (m.getClassName().equals("Point")) {
 *             System.out.println(m.getMethodName() + " line: "
 *                                + m.getLineNumber());
 *     }
 * });
 * </pre>
 *
 * <p>
 * This code inspects all method calls appearing in the method represented
 * by <code>cm</code> and it prints the names and the line numbers of the
 * methods declared in class <code>Point</code>. This code does not modify
 * the body of the method represented by <code>cm</code>. If the method
 * body must be modified, call <code>replace()</code>
 * in <code>MethodCall</code>.
 *
 * @see com.feilong.lib.javassist.CtClass#instrument(ExprEditor)
 * @see com.feilong.lib.javassist.CtMethod#instrument(ExprEditor)
 * @see com.feilong.lib.javassist.CtConstructor#instrument(ExprEditor)
 * @see MethodCall
 * @see NewExpr
 * @see FieldAccess
 *
 * @see com.feilong.lib.javassist.CodeConverter
 */
public class ExprEditor{

    /**
     * Default constructor. It does nothing.
     */
    public ExprEditor(){
    }

    /**
     * Undocumented method. Do not use; internal-use only.
     */
    public boolean doit(CtClass clazz,MethodInfo minfo) throws CannotCompileException{
        CodeAttribute codeAttr = minfo.getCodeAttribute();
        if (codeAttr == null){
            return false;
        }

        CodeIterator iterator = codeAttr.iterator();
        boolean edited = false;
        LoopContext context = new LoopContext(codeAttr.getMaxLocals());

        while (iterator.hasNext()){
            if (loopBody(iterator, clazz, minfo, context)){
                edited = true;
            }
        }

        ExceptionTable et = codeAttr.getExceptionTable();
        int n = et.size();
        for (int i = 0; i < n; ++i){
            Handler h = new Handler(et, i, iterator, clazz, minfo);
            edit(h);
            if (h.edited()){
                edited = true;
                context.updateMax(h.locals(), h.stack());
            }
        }

        // codeAttr might be modified by other partiess
        // so I check the current value of max-locals.
        if (codeAttr.getMaxLocals() < context.maxLocals){
            codeAttr.setMaxLocals(context.maxLocals);
        }

        codeAttr.setMaxStack(codeAttr.getMaxStack() + context.maxStack);
        try{
            if (edited){
                minfo.rebuildStackMapIf6(clazz.getClassPool(), clazz.getClassFile2());
            }
        }catch (BadBytecode b){
            throw new CannotCompileException(b.getMessage(), b);
        }

        return edited;
    }

    /**
     * Visits each bytecode in the given range.
     */
    boolean doit(CtClass clazz,MethodInfo minfo,LoopContext context,CodeIterator iterator,int endPos) throws CannotCompileException{
        boolean edited = false;
        while (iterator.hasNext() && iterator.lookAhead() < endPos){
            int size = iterator.getCodeLength();
            if (loopBody(iterator, clazz, minfo, context)){
                edited = true;
                int size2 = iterator.getCodeLength();
                if (size != size2){
                    endPos += size2 - size;
                }
            }
        }

        return edited;
    }

    final static class NewOp{

        NewOp  next;

        int    pos;

        String type;

        NewOp(NewOp n, int p, String t){
            next = n;
            pos = p;
            type = t;
        }
    }

    final static class LoopContext{

        NewOp newList;

        int   maxLocals;

        int   maxStack;

        LoopContext(int locals){
            maxLocals = locals;
            maxStack = 0;
            newList = null;
        }

        void updateMax(int locals,int stack){
            if (maxLocals < locals){
                maxLocals = locals;
            }

            if (maxStack < stack){
                maxStack = stack;
            }
        }
    }

    final boolean loopBody(CodeIterator iterator,CtClass clazz,MethodInfo minfo,LoopContext context) throws CannotCompileException{
        try{
            Expr expr = null;
            int pos = iterator.next();
            int c = iterator.byteAt(pos);

            if (c < Opcode.GETSTATIC){
                /* skip */;
            }else if (c < Opcode.NEWARRAY){ // c < 188
                if (c == Opcode.INVOKESTATIC || c == Opcode.INVOKEINTERFACE || c == Opcode.INVOKEVIRTUAL){
                    expr = new MethodCall(pos, iterator, clazz, minfo);
                    edit((MethodCall) expr);
                }else if (c == Opcode.GETFIELD || c == Opcode.GETSTATIC || c == Opcode.PUTFIELD || c == Opcode.PUTSTATIC){
                    expr = new FieldAccess(pos, iterator, clazz, minfo, c);
                    edit((FieldAccess) expr);
                }else if (c == Opcode.NEW){
                    int index = iterator.u16bitAt(pos + 1);
                    context.newList = new NewOp(context.newList, pos, minfo.getConstPool().getClassInfo(index));
                }else if (c == Opcode.INVOKESPECIAL){
                    NewOp newList = context.newList;
                    if (newList != null && minfo.getConstPool().isConstructor(newList.type, iterator.u16bitAt(pos + 1)) > 0){
                        expr = new NewExpr(pos, iterator, clazz, minfo, newList.type, newList.pos);
                        edit((NewExpr) expr);
                        context.newList = newList.next;
                    }else{
                        MethodCall mcall = new MethodCall(pos, iterator, clazz, minfo);
                        if (mcall.getMethodName().equals(MethodInfo.nameInit)){
                            ConstructorCall ccall = new ConstructorCall(pos, iterator, clazz, minfo);
                            expr = ccall;
                            edit(ccall);
                        }else{
                            expr = mcall;
                            edit(mcall);
                        }
                    }
                }
            }else{ // c >= 188
                if (c == Opcode.NEWARRAY || c == Opcode.ANEWARRAY || c == Opcode.MULTIANEWARRAY){
                    expr = new NewArray(pos, iterator, clazz, minfo, c);
                    edit((NewArray) expr);
                }else if (c == Opcode.INSTANCEOF){
                    expr = new Instanceof(pos, iterator, clazz, minfo);
                    edit((Instanceof) expr);
                }else if (c == Opcode.CHECKCAST){
                    expr = new Cast(pos, iterator, clazz, minfo);
                    edit((Cast) expr);
                }
            }

            if (expr != null && expr.edited()){
                context.updateMax(expr.locals(), expr.stack());
                return true;
            }
            return false;
        }catch (BadBytecode e){
            throw new CannotCompileException(e);
        }
    }

    /**
     * Edits a <code>new</code> expression (overridable).
     * The default implementation performs nothing.
     *
     * @param e
     *            the <code>new</code> expression creating an object.
     */
    public void edit(NewExpr e) throws CannotCompileException{
    }

    /**
     * Edits an expression for array creation (overridable).
     * The default implementation performs nothing.
     *
     * @param a
     *            the <code>new</code> expression for creating an array.
     * @throws CannotCompileException
     */
    public void edit(NewArray a) throws CannotCompileException{
    }

    /**
     * Edits a method call (overridable).
     *
     * The default implementation performs nothing.
     */
    public void edit(MethodCall m) throws CannotCompileException{
    }

    /**
     * Edits a constructor call (overridable).
     * The constructor call is either
     * <code>super()</code> or <code>this()</code>
     * included in a constructor body.
     *
     * The default implementation performs nothing.
     *
     * @see #edit(NewExpr)
     */
    public void edit(ConstructorCall c) throws CannotCompileException{
    }

    /**
     * Edits a field-access expression (overridable).
     * Field access means both read and write.
     * The default implementation performs nothing.
     */
    public void edit(FieldAccess f) throws CannotCompileException{
    }

    /**
     * Edits an instanceof expression (overridable).
     * The default implementation performs nothing.
     */
    public void edit(Instanceof i) throws CannotCompileException{
    }

    /**
     * Edits an expression for explicit type casting (overridable).
     * The default implementation performs nothing.
     */
    public void edit(Cast c) throws CannotCompileException{
    }

    /**
     * Edits a catch clause (overridable).
     * The default implementation performs nothing.
     */
    public void edit(Handler h) throws CannotCompileException{
    }
}
